package com.sk.util;

import com.alibaba.fastjson.JSONObject;
import com.sk.util.convert.ConvertPoint;
import com.sk.util.convert.ScenePoint;
import com.sk.util.convert.DataConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 常用转换工具。
 *
 * @author smy
 * {@code @date} 2024/5/6
 */
public class ConvertUtil {
    private static final Map<Class, DataConverter> DATA_CONVERTER_MAP = new ConcurrentHashMap<>();

    @SuppressWarnings("unchecked")
    private static Object convertValue(Class<? extends DataConverter> converterClass, Object value) {
        DataConverter converter = DATA_CONVERTER_MAP.get(converterClass);
        if (converter == null) {
            converter = ObjectUtil.newInstance(converterClass);
            DATA_CONVERTER_MAP.put(converterClass, converter);
        }
        return converter.getResult(value);
    }

    private static ScenePoint findPoint(ScenePoint[] scenePoints, String scene) {
        for (ScenePoint scenePoint : scenePoints) {
            if (Objects.equals(scene, scenePoint.value())) {
                return scenePoint;
            }
        }
        for (ScenePoint scenePoint : scenePoints) {
            if (Objects.equals("", scenePoint.value())) {
                return scenePoint;
            }
        }
        return null;
    }

    private static List<String> getNamesByPoint(ScenePoint scenePoint, String name) {
        List<String> names = Arrays.asList(scenePoint.name());
        names.add(name);
        return names.stream().filter(StringUtils::hasText).distinct().collect(Collectors.toList());
    }


    private static Object getValueByPoint(ScenePoint scenePoint, List<String> names, Map<String, Object> param) {
        Object value = null;
        for (String name : names) {
            value = param.get(name);
            if (value != null) {
                break;
            }
        }
        return convertValue(scenePoint.converter(), value);
    }

    private static Object[] newArgsByPoint(Parameter[] parameters, Object[] args) {
        Object[] argsNew = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            ConvertPoint dataPoint = parameters[i].getAnnotation(ConvertPoint.class);
            Object value = args[i];
            argsNew[i] = dataPoint != null ? convertValue(dataPoint.value(), value) : value;
        }
        return argsNew;
    }

    /**
     * 将map转为bean对象
     *
     * @param map 源数据
     * @param c   对象类型
     * @param <T> 对象类型约束
     * @return 对象
     * @see ScenePoint
     */
    public static <T> T map2bean(Map<String, Object> map, Class<T> c) {
        return map2bean(map, c, "");
    }

    /**
     * 将map转为bean对象
     *
     * @param map   源数据
     * @param c     对象类型
     * @param scene 场景定义
     * @param <T>   对象类型约束
     * @return 对象
     * @see ScenePoint
     */
    public static <T> T map2bean(Map<String, Object> map, Class<T> c, String scene) {
        T t = ObjectUtil.newInstance(c);
        map2bean(map, t, scene);
        return t;
    }

    /**
     * 将map转为bean对象
     *
     * @param map 源数据
     * @param o   目标数据
     * @see ScenePoint
     */
    public static void map2bean(Map<String, Object> map, Object o) {
        map2bean(map, o, "");
    }

    /**
     * 将map转为bean对象
     *
     * @param map   源数据
     * @param o     目标数据
     * @param scene 场景定义
     * @see ScenePoint
     */
    public static void map2bean(Map<String, Object> map, Object o, String scene) {
        List<Field> fields = ObjectUtil.getFields(o.getClass());
        for (Field field : fields) {
            ScenePoint scenePoint = findPoint(field.getAnnotationsByType(ScenePoint.class), scene);
            if (scenePoint == null) {
                continue;
            }
            List<String> names = getNamesByPoint(scenePoint, field.getName());
            Object value = getValueByPoint(scenePoint, names, map);
            field.setAccessible(true);
            ObjectUtil.setValue(o, field, value);
        }
    }

    /**
     * 将bean对象转map
     *
     * @param o 源数据
     * @return JSONObject
     * @see ScenePoint
     */
    public static JSONObject bean2json(Object o) {
        return bean2json(o, "");
    }

    /**
     * 将bean对象转map
     *
     * @param o     源数据
     * @param scene 场景定义
     * @return JSONObject
     * @see ScenePoint
     */
    public static JSONObject bean2json(Object o, String scene) {
        List<Field> fields = ObjectUtil.getFields(o.getClass());
        JSONObject jsonObject = new JSONObject();
        for (Field field : fields) {
            ScenePoint scenePoint = findPoint(field.getAnnotationsByType(ScenePoint.class), scene);
            if (scenePoint == null) {
                continue;
            }
            field.setAccessible(true);
            Object value = convertValue(scenePoint.converter(), ObjectUtil.getValue(o, field));
            String name = getNamesByPoint(scenePoint, field.getName()).get(0);
            jsonObject.put(name, value);
        }
        return jsonObject;
    }

    /**
     * 基于工厂方法获取一个Function
     *
     * @param c          类名
     * @param methodName 方法名
     * @param <V>        泛型约束
     * @return Function
     */
    public static <V> Function<Object, V> genFactory(Class<?> c, String methodName) {
        return genFactory(ObjectUtil.getMethodByName(c, methodName));
    }

    /**
     * 基于工厂方法获取一个Function
     *
     * @param method 工厂方法
     * @param <V>    泛型约束
     * @return Function
     */
    public static <V> Function<Object, V> genFactory(Method method) {
        Assert.isTrue(Modifier.isStatic(method.getModifiers()), "方法必须是静态方法");
        return o -> {
            Object[] args = o.getClass().isArray() ? (Object[]) o : new Object[]{o};
            Object[] newArgs = newArgsByPoint(method.getParameters(), args);
            return ObjectUtil.method(null, method, newArgsByPoint(method.getParameters(), newArgs));
        };
    }

    /**
     * 基于构造器获取一个Function
     *
     * @param vClass 类名
     * @param pTypes 参数类型
     * @param <V>    泛型约束
     * @return Function
     */
    public static <V> Function<Object, V> genFactory(Class<V> vClass, Class<?>... pTypes) {
        return genFactory(ObjectUtil.getConstructor(vClass, pTypes));
    }

    /**
     * 基于构造器获取一个Function
     *
     * @param c   构造器
     * @param <V> 泛型约束
     * @return Function
     */
    public static <V> Function<Object, V> genFactory(Constructor<V> c) {
        return o -> {
            Object[] args = o.getClass().isArray() ? (Object[]) o : new Object[]{o};
            Object[] newArgs = newArgsByPoint(c.getParameters(), args);
            return ObjectUtil.newInstance(c, newArgs);
        };
    }
}
